
import java.util.StringTokenizer;

public final class UserInputParser {
	/*
	 * This class interfaces with the UI, gets the user input and parses the string into 
	 */
	private static final String ERROR_PARSING_CONFLICT = "Error: %1$s parsing conflict between \"%2$s\" and \"%3$s\".";
	private static final int PARAM_TWO_ELEMENTS = 2;
	private static final int PARAM_ONE_ELEMENT = 1;
	private static final String ERROR_CMD_VERIFICATION = "Error: Command verification failed";
	private final int cmdIndex = 0;
	private final int paramIndex = 1;
	private final Task.TaskAction PARAM_TASK_UPDATE = Task.TaskAction.UPDATE;
	private final Task.TaskAction PARAM_TASK_ADD = Task.TaskAction.ADD;
	private final Task.TaskAction PARAM_TASK_DELETE = Task.TaskAction.DELETE;
	private final Task.TaskAction PARAM_TASK_VIEW = Task.TaskAction.VIEW;
	private final Task.TaskAction PARAM_TASK_UNDO = Task.TaskAction.UNDO;
	private final String PARAM_SEPERATOR = ",";
	private final String PARAM_ACTION_TYPE = "action";
	private final String PARAM_CMD_ADD = "add";
	private final String PARAM_CMD_TO = "to";
	private final String PARAM_CMD_FROM = "from";
	private final String PARAM_CMD_BY = "by";
	private final String PARAM_CMD_ON = "on";
	private final String PARAM_CMD_STAR = "star";
	private final String PARAM_CMD_DONE = "done";
	private final String PARAM_CMD_DELETE = "delete";
	private final String PARAM_CMD_VIEW = "view";
	private final String PARAM_CMD_UNDO = "undo";
	private final String PARAM_CMD_EXIT = "exit";
	private final String PARAM_CMD_UPDATE = "update";
	private final String PARAM_CMD_SYNC = "sync";
	private final String PARAM_CMD_ACTION_TYPE = "action";
	private final String PARAM_CMD_UNDEFINED = "";
	private final String ERROR_PARSING_ERROR = "Error: Parsing error!";
	private final String PARAM_STRING_RECONSTRUCT = "%1$s %2$s";
	public Task task;
	private boolean isTitleSet;
	private boolean isStartDateSet;
	private boolean isEndDateSet;
	private boolean isDescriptionSet;
	private boolean isStarSet;
	private boolean isDoneSet;
	private boolean isActionSet;

	private CommandType convCmdToEnum(String string) {
		CommandType result = CommandType.UNDEFINED;
		if (string.equals(PARAM_CMD_ADD)) {
			result = CommandType.ADD;
		}else if (string.equals(PARAM_CMD_TO)) {
			result = CommandType.TO;
		}else if (string.equals(PARAM_CMD_FROM)) {
			result = CommandType.FROM;
		}else if (string.equals(PARAM_CMD_BY)) {
			result = CommandType.BY;
		}else if (string.equals(PARAM_CMD_ON)) {
			result = CommandType.ON;
		}else if (string.equals(PARAM_CMD_STAR)) {
			result = CommandType.STAR;
		}else if (string.equals(PARAM_CMD_DONE)) {
			result = CommandType.DONE;
		}else if (string.equals(PARAM_CMD_DELETE)) {
			result = CommandType.DELETE;
		}else if (string.equals(PARAM_CMD_VIEW)) {
			result = CommandType.VIEW;
		}else if (string.equals(PARAM_CMD_UNDO)) {
			result = CommandType.UNDO;
		}else if (string.equals(PARAM_CMD_SYNC)) {
			result = CommandType.SYNC;
		}else if (string.equals(PARAM_CMD_EXIT)) {
			result = CommandType.EXIT;
		}else if (string.equals(PARAM_CMD_UPDATE)) {
			result = CommandType.UPDATE;
		}else if (string.equals(PARAM_CMD_ACTION_TYPE)) {
			result = CommandType.ACTION_TYPE;
		}
		return result;
	}

	public enum CommandType {

		ADD, TO, FROM, BY, ON, STAR, DONE, DELETE, VIEW, UNDO, SYNC, EXIT, UPDATE, ACTION_TYPE, UNDEFINED
	}

	public UserInputParser() {
		initializeMembers();
	}

	/**
	 * Constructor 
	 * @param userInput
	 */
	 public UserInputParser(String userInput) {
		initializeMembers();
		this.parseUserInput(userInput);
	}

	/**
	 * Parses the user input
	 * @param userInput
	 */
	 public void parseUserInput(String userInput) {
		 this.task = new Task();
		 StringTokenizer inputToken = new StringTokenizer(userInput, PARAM_SEPERATOR);
		 analyzeToken(inputToken);
		 task.doTask();
	 }

	 /**
	  * 
	  * @return
	  */
	 private Task getTask() {
		 return task;
	 }

	 /**
	  * 
	  * @param mixCmdAndParam
	  */
	 private void seperateInputElement(String mixCmdAndParam) {
		 String[] cmdAndParam;
		 cmdAndParam = mixCmdAndParam.split(" ", 2);
		 addElements(cmdAndParam);
	 }

	 /**
	  * 
	  * @param inputToken
	  */
	 private void analyzeToken(StringTokenizer inputToken) {
		 while (inputToken.hasMoreTokens()) {
			 String element = inputToken.nextToken();
			 element = trimLeft(element);
			 element = trimRight(element);
			 seperateInputElement(element);
		 }
	 }

	 /**
	  * 
	  * @param string
	  * @return
	  */
	 private static String trimLeft(String string) {
		 final String regexpLeftSpace = "^\\s+";
		 final String emptyString = "";

		 return string.replaceAll(regexpLeftSpace, emptyString);
	 }

	 /**
	  * 
	  * @param string
	  * @return
	  */
	 private static String trimRight(String string) {
		 final String regexpRightSpace = "\\s+$";
		 final String emptyString = "";

		 return string.replaceAll(regexpRightSpace, emptyString);
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void addElements(String[] cmdAndParam) {

		 if (isNumberOfInputElements(cmdAndParam, PARAM_TWO_ELEMENTS)) {
			 addBasicCmdAndParam(cmdAndParam);
		 } else if (isNumberOfInputElements(cmdAndParam, PARAM_ONE_ELEMENT) && isActionSet()) {
			 addOneCmdOnAction(cmdAndParam);
		 } else if (cmdAndParam[cmdIndex].equals(PARAM_CMD_UNDO) || cmdAndParam[cmdIndex].equals(PARAM_CMD_EXIT) || cmdAndParam[cmdIndex].equals(PARAM_CMD_SYNC) ) {
			 oneCmd(cmdAndParam);
		 } else {
			 TaskO.taskOMainScreen.appendOutput(ERROR_CMD_VERIFICATION);
		 }
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void descriptionEleAction(String[] cmdAndParam) {
		 if (isDescriptionSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else if (cmdAndParam.length == PARAM_TWO_ELEMENTS) {
			 String reconstructString = elementStringReconstruct(cmdAndParam);
			 task.setTaskDescription(reconstructString);
		 } else if (cmdAndParam.length == PARAM_ONE_ELEMENT) {
			 task.setTaskDescription(cmdAndParam[cmdIndex]);
		 } else {
			 TaskO.taskOMainScreen.appendOutput(ERROR_PARSING_ERROR);
		 }
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void addEleAction(String[] cmdAndParam) {

		 if (isTitleSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskTitle(cmdAndParam[paramIndex]);
			 isTitleSet = true;
		 }

		 if (isActionSet) {
			 String errorMsg = errorActionConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskAction(PARAM_TASK_ADD);
			 isActionSet = true;
		 }

	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void endDateEleAction(String[] cmdAndParam) {
		 if (isEndDateSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskEndDate(cmdAndParam[paramIndex]);
			 this.isEndDateSet = true;
		 }

	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void startDateEleAction(String[] cmdAndParam) {
		 if (isStartDateSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskStartDate(cmdAndParam[paramIndex]);
			 isStartDateSet = true;
		 }

	 }
	 //TODO: condition checking before setting elements
	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void starEleAction(String[] cmdAndParam) {
		 if (isStarSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setIsTaskStarred(true);
			 isStarSet = true;
		 }
	 }
	 
	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void doneEleAction(String[] cmdAndParam) {
		 if (isDoneSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setIsTaskDone(true);
			 isDoneSet = true;
		 }

	 }
	 
	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void updateEleAction(String[] cmdAndParam) {
		 if (isActionSet) {
			 String errorMsg = errorActionConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskAction(PARAM_TASK_UPDATE);
			 isActionSet = true;
		 }

		 if (isDescriptionSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskDescription(cmdAndParam[paramIndex]);
			 isDescriptionSet = true;
		 }

	 }
	 
	 private void initializeMembers() {

		 isTitleSet = false;
		 isStartDateSet = false;
		 isEndDateSet = false;
		 isDescriptionSet = false;
		 isStarSet = false;
		 isDoneSet = false;
		 isActionSet = false;
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void deleteEleAction(String[] cmdAndParam) {
		 if (isActionSet) {
			 String errorMsg = errorActionConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskAction(PARAM_TASK_DELETE);
			 isActionSet = true;
		 }

		 if (isDescriptionSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskDescription(cmdAndParam[paramIndex]);
			 isDescriptionSet = true;
		 }
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void viewEleAcion(String[] cmdAndParam) {
		 if (isActionSet) {
			 String errorMsg = errorActionConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskAction(PARAM_TASK_VIEW);
			 isActionSet = true;
		 }

		 if (isDescriptionSet) {
			 String errorMsg = errorEleConflictMsg(cmdAndParam);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskDescription(cmdAndParam[paramIndex]);
			 isDescriptionSet = true;
		 }
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  * @return
	  */
	 private String elementStringReconstruct(String[] cmdAndParam) {
		 String reconstructString;

		 if (cmdAndParam.length == PARAM_TWO_ELEMENTS) {
			 reconstructString = String.format(PARAM_STRING_RECONSTRUCT, cmdAndParam[cmdIndex], cmdAndParam[paramIndex]);
		 } else if (cmdAndParam.length == PARAM_ONE_ELEMENT) {
			 reconstructString = cmdAndParam[cmdIndex];
		 } else {
			 TaskO.taskOMainScreen.appendOutput("Error: reconstructing string.");
			 return null;
		 }
		 return reconstructString;
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  * @return
	  */
	 private String errorActionConflictMsg(String[] cmdAndParam) {
		 final String errorType = PARAM_ACTION_TYPE;
		 String curString = elementStringReconstruct(cmdAndParam);
		 String lastElement = getElementByCmd(convCmdToEnum(errorType));
		 String prevString = "existing " + lastElement;
		 String errorMsg = String.format(ERROR_PARSING_CONFLICT, errorType, curString, prevString);
		 return errorMsg;
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  * @return
	  */
	 private String errorEleConflictMsg(String[] cmdAndParam) {
		 final String errorType = cmdAndParam[cmdIndex];
		 String curString = elementStringReconstruct(cmdAndParam);
		 String lastElement = getElementByCmd(convCmdToEnum(errorType));
		 String prevString = "existing " + lastElement;
		 String errorMsg = String.format(ERROR_PARSING_CONFLICT, errorType, curString, prevString);
		 return errorMsg;
	 }

	 /**
	  * 
	  * @param cmd
	  * @return
	  */
	 private String errorActionConflictMsg(String cmd) {
		 final String errorType = cmd;
		 String curString = cmd;
		 String lastElement = getElementByCmd(convCmdToEnum(errorType));
		 String prevString = "existing " + lastElement;
		 String errorMsg = String.format(ERROR_PARSING_CONFLICT, errorType, curString, prevString);
		 return errorMsg;
	 }

	 /**
	  * 
	  * @param command
	  * @return
	  */
	 private String getElementByCmd(CommandType command) {
		 String element;
		 switch (command) {
		 case ADD:
			 element = task.getTaskTitle();
			 break;
		 case TO:
			 element = task.getTaskEndDate();
			 break;
		 case FROM:
			 element = task.getTaskStartDate();
			 break;
		 case BY:
			 element = task.getTaskEndDate();
			 break;
		 case ON:
			 element = task.getTaskStartDate();
			 break;
		 case STAR:
			 element = task.getIsTaskStarred() ? "starred" : "not starred";
			 break;
		 case DONE:
			 element = task.getIsTaskStarred() ? "done" : "not done";
			 break;
		 case UPDATE:
			 element = task.getTaskAction().toString();
			 break;
		 case DELETE:
			 element = task.getTaskAction().toString();
			 break;
		 case VIEW:
			 element = task.getTaskAction().toString();
			 break;
		 case ACTION_TYPE:
			 element = task.getTaskAction().toString();
			 break;
		 default:
			 element = task.getTaskDescription();
			 break;
		 }
		 return element;
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  * @param numberOfElement
	  * @return
	  */
	 private boolean isNumberOfInputElements(String[] cmdAndParam, int numberOfElement) {
		 return cmdAndParam.length == numberOfElement;
	 }

	 /**
	  * 
	  * @return
	  */
	 private boolean isActionSet() {
		 UserInputElements.TaskAction actionNotSet = UserInputElements.TaskAction.UNDEFINED;
		 return !(task.getTaskAction().equals(actionNotSet));
	 }

	 /**
	  * 
	  */
	 private void undoEleAction() {
		 if (isActionSet) {
			 String errorMsg = errorActionConflictMsg(PARAM_CMD_UNDO);
			 TaskO.taskOMainScreen.appendOutput(errorMsg);
		 } else {
			 task.setTaskAction(PARAM_TASK_UNDO);
			 isActionSet = true;
		 }
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void addBasicCmdAndParam(String[] cmdAndParam) {
		 String stringCmd = cmdAndParam[cmdIndex];
		 CommandType inputParam = convCmdToEnum(cmdAndParam[cmdIndex]);

		 switch (inputParam) {
		 case ADD:
			 addEleAction(cmdAndParam);
			 break;
		 case TO:
			 endDateEleAction(cmdAndParam);
			 break;
		 case FROM:
			 startDateEleAction(cmdAndParam);
			 break;
		 case BY:
			 endDateEleAction(cmdAndParam);
			 break;
		 case ON:
			 startDateEleAction(cmdAndParam);
			 break;
		 case STAR:
			 starEleAction(cmdAndParam);
			 break;
		 case DONE:
			 doneEleAction(cmdAndParam);
			 break;
		 case UPDATE:
			 updateEleAction(cmdAndParam);
			 break;
		 case DELETE:
			 deleteEleAction(cmdAndParam);
			 break;
		 case VIEW:
			 viewEleAcion(cmdAndParam);
			 break;
		 default:
			 descriptionEleAction(cmdAndParam);
			 break;
		 }
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void addOneCmdOnAction(String[] cmdAndParam) {
		 String stringCmd = cmdAndParam[cmdIndex];
		 CommandType inputParam = convCmdToEnum(cmdAndParam[cmdIndex]);
		 switch (inputParam) {
		 case STAR:
			 starEleAction(cmdAndParam);
			 break;
		 case DONE:
			 doneEleAction(cmdAndParam);
			 break;
		 default:
			 descriptionEleAction(cmdAndParam);
			 break;
		 }
	 }

	 /**
	  * 
	  * @param cmdAndParam
	  */
	 private void oneCmd(String[] cmdAndParam) {
		 String stringCmd = cmdAndParam[cmdIndex];
		 CommandType inputParam = convCmdToEnum(cmdAndParam[cmdIndex]);
		 switch (inputParam) {
		 case UNDO:
			 undoEleAction();
			 break;
		 case EXIT:
			 System.exit(0);
			 break;
		 case SYNC:
			 break;
		 default:
			 break;
		 }
	 }
}
